﻿using System;
using System.IO;

namespace SHA1
{
    class SHA1
    {
        public SHA1()
        {   
			AutoReset = false;
        }

        public byte[] ComputeHash(byte[] bytes)
        {
            long msgSize = bytes.Length;
            // Długość wiadomości w bitach
            ulong msgBitSize = (ulong)msgSize << 3;
            byte[] block = new byte[64];
            uint[] words = new uint[80];
            block[0] = 1;
            long position = 0;
            long diff = msgSize - position;

            while (position < msgSize)
            {
                if (64 <= diff)
                {
                    Array.Copy(bytes, position, block, 0, 64);
                    position += 64;
                }
                else
                    break;
                Convert(block, words);
                Transform(words);
                diff = msgSize - position;
            }

            Array.Clear(block, 0, 64);
            if (diff > 0)
                Array.Copy(bytes, position, block, 0, diff);
            // Dostawiamy jedynkę na końcu wiadomości
            block[diff] = 0x80;
            // Jeśli nie starczyło miejsca na licznik znaków w wiadomości...
            if (64 - (diff + 1) < 8)
            {
                Convert(block, words);
                Transform(words);
                Array.Clear(block, 0, 64);
            }

            // Wstawiamy licznik na końcu bloku
            block[56] = (byte)(msgBitSize >> 56);
            block[57] = (byte)((msgBitSize >> 48) & 0xFF);
            block[58] = (byte)((msgBitSize >> 40) & 0xFF);
            block[59] = (byte)((msgBitSize >> 32) & 0xFF);
            block[60] = (byte)((msgBitSize >> 24) & 0xFF);
            block[61] = (byte)((msgBitSize >> 16) & 0xFF);
            block[62] = (byte)((msgBitSize >> 8) & 0xFF);
            block[63] = (byte)(msgBitSize & 0xFF);

            Convert(block, words);
            Transform(words);

            // Przepisujemy hash na bajty.
            byte[] hash = new byte[20];
            hash[0] = (byte)((h0 >> 24) & 0xFF);
            hash[1] = (byte)((h0 >> 16) & 0xFF);
            hash[2] = (byte)((h0 >> 8) & 0xFF);
            hash[3] = (byte)(h0 & 0xFF);

            hash[4] = (byte)((h1 >> 24) & 0xFF);
            hash[5] = (byte)((h1 >> 16) & 0xFF);
            hash[6] = (byte)((h1 >> 8) & 0xFF);
            hash[7] = (byte)(h1 & 0xFF);

            hash[8] = (byte)((h2 >> 24) & 0xFF);
            hash[9] = (byte)((h2 >> 16) & 0xFF);
            hash[10] = (byte)((h2 >> 8) & 0xFF);
            hash[11] = (byte)(h2 & 0xFF);

            hash[12] = (byte)((h3 >> 24) & 0xFF);
            hash[13] = (byte)((h3 >> 16) & 0xFF);
            hash[14] = (byte)((h3 >> 8) & 0xFF);
            hash[15] = (byte)(h3 & 0xFF);

            hash[16] = (byte)((h4 >> 24) & 0xFF);
            hash[17] = (byte)((h4 >> 16) & 0xFF);
            hash[18] = (byte)((h4 >> 8) & 0xFF);
            hash[19] = (byte)(h4 & 0xFF);

            if (AutoReset)
                Reset();
            return hash;
        }

        public byte[] ComputeHash(Stream reader)
        {
            long fileSize = reader.Length;
            // Długość wiadomości w bitach
            ulong msgBitSize = (ulong)fileSize << 3;
            byte[] block = new byte[64];
            uint[] words = new uint[80];
            block[0] = 1;
            long diff = fileSize - reader.Position;

            // Odczytujemy kolejne bloki z pliku i przetwarzamy
            while (reader.Position < fileSize)
            {
                if (64 <= diff)
                    reader.Read(block, 0, 64);
                else
                    break;
                Convert(block, words);
                Transform(words);
                diff = fileSize - reader.Position;
            }

            Array.Clear(block, 0, 64);
            if (diff > 0)
                reader.Read(block, 0, (int)diff);
            // Dostawiamy jedynkę na końcu wiadomości
            block[diff] = 0x80;
            // Jeśli nie starczyło miejsca na licznik znaków w wiadomości...
            if (64 - (diff + 1) < 8)
            {
                Convert(block, words);
                Transform(words);
                Array.Clear(block, 0, 64);
            }

            // Wstawiamy licznik na końcu bloku
            block[56] = (byte)(msgBitSize >> 56);
            block[57] = (byte)((msgBitSize >> 48) & 0xFF);
            block[58] = (byte)((msgBitSize >> 40) & 0xFF);
            block[59] = (byte)((msgBitSize >> 32) & 0xFF);
            block[60] = (byte)((msgBitSize >> 24) & 0xFF);
            block[61] = (byte)((msgBitSize >> 16) & 0xFF);
            block[62] = (byte)((msgBitSize >> 8) & 0xFF);
            block[63] = (byte)(msgBitSize & 0xFF);

            Convert(block, words);
            Transform(words);

            reader.Dispose();
            // Przepisujemy hash na bajty.
            byte[] hash = new byte[20];
            hash[0] = (byte)((h0 >> 24) & 0xFF);
            hash[1] = (byte)((h0 >> 16) & 0xFF);
            hash[2] = (byte)((h0 >> 8) & 0xFF);
            hash[3] = (byte)(h0 & 0xFF);

            hash[4] = (byte)((h1 >> 24) & 0xFF);
            hash[5] = (byte)((h1 >> 16) & 0xFF);
            hash[6] = (byte)((h1 >> 8) & 0xFF);
            hash[7] = (byte)(h1 & 0xFF);

            hash[8] = (byte)((h2 >> 24) & 0xFF);
            hash[9] = (byte)((h2 >> 16) & 0xFF);
            hash[10] = (byte)((h2 >> 8) & 0xFF);
            hash[11] = (byte)(h2 & 0xFF);

            hash[12] = (byte)((h3 >> 24) & 0xFF);
            hash[13] = (byte)((h3 >> 16) & 0xFF);
            hash[14] = (byte)((h3 >> 8) & 0xFF);
            hash[15] = (byte)(h3 & 0xFF);

            hash[16] = (byte)((h4 >> 24) & 0xFF);
            hash[17] = (byte)((h4 >> 16) & 0xFF);
            hash[18] = (byte)((h4 >> 8) & 0xFF);
            hash[19] = (byte)(h4 & 0xFF);

            return hash;
        }


        public void Reset()
        {
            h0 = 0x67452301;
            h1 = 0xEFCDAB89;
            h2 = 0x98BADCFE;
            h3 = 0x10325476;
            h4 = 0xC3D2E1F0;
        }

        public bool AutoReset { get; set; }

        private void Transform(uint[] words)
        {
            uint a = h0, b = h1, c = h2, d = h3, e = h4;

            for (int i = 0; i < 20; ++i)
            {
                uint f = (b & c) | (~b & d);
                uint s = (a << 5) | (a >> 27);
                uint temp = s + f + e + 0x5A827999 + words[i];
                e = d;
                d = c;
                c = (b << 30) | (b >> 2);
                b = a;
                a = temp;
            }

            for (int i = 20; i < 40; ++i)
            {
                uint f = b ^ c ^ d;
                uint s = (a << 5) | (a >> 27);
                uint temp = s + f + e + 0x6ED9EBA1 + words[i];
                e = d;
                d = c;
                c = (b << 30) | (b >> 2);
                b = a;
                a = temp;
            }

            for (int i = 40; i < 60; ++i)
            {
                uint f = (b & c) | (b & d) | (c & d);
                uint s = (a << 5) | (a >> 27);
                uint temp = s + f + e + 0x8F1BBCDC + words[i];
                e = d;
                d = c;
                c = (b << 30) | (b >> 2);
                b = a;
                a = temp;
            }

            for (int i = 60; i < 80; ++i)
            {
                uint f = b ^ c ^ d;
                uint s = (a << 5) | (a >> 27);
                uint temp = s + f + e + 0xCA62C1D6 + words[i];
                e = d;
                d = c;
                c = (b << 30) | (b >> 2);
                b = a;
                a = temp;
            }

            h0 += a;
            h1 += b;
            h2 += c;
            h3 += d;
            h4 += e;
        }

        private static void Convert(byte[] bytes, uint[] words)
        {
            for (int i = 0; i < 16; ++i)
                words[i] = ((uint)bytes[i << 2] << 24) | ((uint)bytes[(i << 2) + 1] << 16) | ((uint)bytes[(i << 2) + 2] << 8) | ((uint)bytes[(i << 2) + 3]);
            for (int i = 16; i < 80; ++i)
            {
                uint x = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16];
                words[i] = (x << 1) | (x >> 31);
            }
        }

        private uint h0 = 0x67452301;
        private uint h1 = 0xEFCDAB89;
        private uint h2 = 0x98BADCFE;
        private uint h3 = 0x10325476;
        private uint h4 = 0xC3D2E1F0;
    }		
}
